﻿using System;
using System.Collections.Generic;
using System.Windows.Documents;

namespace Bookvar.ObjectModel.Actions
{
    /// <summary>
    /// This action changes a topic.
    /// </summary>
    public class ChangeTopicAction : TopicActionBase
    {
        private readonly UndoRedoCollectionHelper<Attachment> attachmentsUndoRedoHelper =
            new UndoRedoCollectionHelper<Attachment>();

        private readonly UndoRedoCollectionHelper<Resource> resourcesUndoRedoHelper =
            new UndoRedoCollectionHelper<Resource>();

        private Uri hyperlink;
        private TextRange notes;
        private Uri oldHyperLink;
        private string oldName;
        private TextRange oldNotes;
        private Topic oldParent;
        private Topic parent;
        private bool wasNewHyperlinkProvided;
        private bool wasNewNotesProvided;
        private bool wasNewParentProvided;

        /// <summary>
        /// Initializes a new instance of the <see cref="ChangeTopicAction"/> class with the target topic 
        /// that will be changed.
        /// </summary>
        public ChangeTopicAction()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ChangeTopicAction"/> class with the target topic 
        /// that will be changed.
        /// </summary>
        /// <param name="target">The target topic.</param>
        /// <exception cref="ArgumentNullException">if <paramref name="target"/> is <c>null</c>.</exception>
        public ChangeTopicAction( Topic target ) : base( target )
        {
            InitializeCollectionHelpers( target );
        }

        /// <summary>
        /// Returns <c>true</c> because this action supports redo.
        /// </summary>
        /// <value>
        /// 	<c>true</c>.
        /// </value>
        public override bool CanRedo
        {
            get { return true; }
        }

        /// <summary>
        /// Returns <c>true</c> because this action supports undo.
        /// </summary>
        /// <value>
        /// 	<c>true</c>.
        /// </value>
        public override bool CanUndo
        {
            get { return true; }
        }

        /// <summary>
        /// Gets or sets the topic name. If this property is set the action will update the name of the topic.
        /// </summary>
        /// <value>The new name.</value>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the parent. If this property is set the action will update the parent of the target topic.
        /// </summary>
        /// <remarks>
        /// If you set the parent to null this will not make the topic floating; the parent will just not be changed.
        /// </remarks>
        /// /// <exception cref="ArgumentNullException">if value is null.</exception>
        /// <value>The parent.</value>
// TODO: Refactor the code so we can make the topic floating or not.
// Now the topic does not know if it is floating or in which MindMap is it.
        public Topic Parent
        {
            get { return parent; }
            set
            {
                if ( value == null )
                {
                    throw new ArgumentNullException( "value" );
                }
                parent = value;
                wasNewParentProvided = true;
            }
        }

        /// <summary>
        /// Gets or sets the new hyper link. If this property is set the action will update the target topic hyper link.
        /// </summary>
        /// <value>The new hyper link.</value>
        // TODO: when NewHyperLink is null - the topic hyper link should be removed.
        // This is impossible for now because you cannot set null to the topic hyperlink property.
        public Uri Hyperlink
        {
            get { return hyperlink; }
            set
            {
                hyperlink = value;
                wasNewHyperlinkProvided = true;
            }
        }

        /// <summary>
        /// Gets or sets the new notes. If this property is set the action will update the topic notes.
        /// </summary>
        /// <value>The new notes.</value>
        public TextRange Notes
        {
            get { return notes; }
            set
            {
                notes = value;
                wasNewNotesProvided = true;
            }
        }

        /// <summary>
        /// Gets or sets the attachments to add to the target topic.
        /// </summary>
        /// If both the <see cref="AttachmentsToAdd"/> and <see cref="AttachmentsToRemove"/> are set,
        /// first we will remove the provided attachments then we will add the new ones.
        /// <value>The attachments to add to the target topic.</value>
        public ICollection<Attachment> AttachmentsToAdd
        {
            get { return attachmentsUndoRedoHelper.ItemsToAdd; }
        }

        /// <summary>
        /// Gets or sets the attachments to remove from the target topic.
        /// </summary>
        /// <remarks>
        /// If both the <see cref="AttachmentsToAdd"/> and <see cref="AttachmentsToRemove"/> are set,
        /// first we will remove the provided attachments then we will add the new ones.
        /// </remarks>
        /// <value>The attachments to remove from the target topic.</value>
        public ICollection<Attachment> AttachmentsToRemove
        {
            get { return attachmentsUndoRedoHelper.ItemsToRemove; }
        }

        /// <summary>
        /// Gets or sets the resources to add to the target topic.
        /// </summary>
        /// <remarks>
        /// If both the <see cref="ResourcesToAdd"/> and <see cref="ResourcesToRemove"/> are set,
        /// first we will remove the provided resources then we will add the new ones.
        /// </remarks>
        /// <value>The resources to add to the target topic.</value>
        public ICollection<Resource> ResourcesToAdd
        {
            get { return resourcesUndoRedoHelper.ItemsToAdd; }
        }

        /// <summary>
        /// Gets or sets the resources to remove from the target topic.
        /// </summary>
        /// <remarks>
        /// If both the <see cref="ResourcesToAdd"/> and <see cref="ResourcesToRemove"/> are set,
        /// first we will remove the provided resources then we will add the new ones.
        /// </remarks>
        /// <value>The resources to remove from the target topic.</value>
        public ICollection<Resource> ResourcesToRemove
        {
            get { return resourcesUndoRedoHelper.ItemsToRemove; }
        }

        /// <summary>
        /// Applies the changes on the target topic.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// If the <see cref="Topic"/> is not set properly or  passed to the constructor;
        /// or if the action is already executed.
        /// </exception>
        public override void Execute()
        {
            base.Execute();

            if ( Name != null )
            {
                oldName = Topic.Name;
                Topic.Name = Name;
            }

            DoNewParent();

            if ( wasNewHyperlinkProvided )
            {
                oldHyperLink = Topic.Hyperlink;
                Topic.Hyperlink = Hyperlink;
            }

            if ( wasNewNotesProvided )
            {
                oldNotes = Topic.Note;
                Topic.Note = Notes;
            }

            attachmentsUndoRedoHelper.Execute();
            resourcesUndoRedoHelper.Execute();
        }

        /// <summary>
        /// Undoes the action.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// If the <see cref="Topic"/> is not set properly or passed to the constructor;
        /// or if called before the action is executed.
        /// </exception>
        public override void Undo()
        {
            base.Undo();

            if ( oldName != null )
            {
                Topic.Name = oldName;
            }

            UndoNewParent();

            if ( wasNewHyperlinkProvided )
            {
                Topic.Hyperlink = oldHyperLink;
            }

            if ( wasNewNotesProvided )
            {
                Topic.Note = oldNotes;
            }

            attachmentsUndoRedoHelper.Undo();
            resourcesUndoRedoHelper.Undo();
        }

        private void DoNewParent()
        {
            if ( parent != null )
            {
                oldParent = Topic.Parent;
                if ( oldParent != null )
                {
                    oldParent.SubTopics.Remove( Topic );
                }

                parent.SubTopics.Add( Topic );
            }
        }

        private void UndoNewParent()
        {
            if ( wasNewParentProvided )
            {
                parent.SubTopics.Remove( Topic );

                if ( oldParent != null )
                {
                    oldParent.SubTopics.Add( Topic );
                }
                else
                {
                    Topic.Parent = null;
                }
            }
        }

        /// <summary>
        /// Called when the <see cref="Topic"/> is changed.
        /// </summary>
        protected override void OnTopicChanged()
        {
            base.OnTopicChanged();

            if ( Topic != null )
            {
                InitializeCollectionHelpers( Topic );
            }
        }

        private void InitializeCollectionHelpers( Topic topic )
        {
            attachmentsUndoRedoHelper.Target = topic.Attachments;
            resourcesUndoRedoHelper.Target = topic.Resources;
        }
    }
}
